<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script>
    /*
      生成器函数 Generator:
        1. 是一个有限状态机，可以封装多个状态
        2. 生成器函数可以返回一个遍历器对象，可依次遍历 Generator 函数内部的每一个状态

      特点:
        1. function* 函数名(){}
        2. 函数体内可以使用 yield 表达式，定义不同的内部状态（yield：产出）
        3. 返回值是一个具有 next 方法的迭代器对象，next 方法返回一个对象（{ value:'', done: false }）; done 表示函数是否执行完成，value 表示当前的状态

      当调用 Generator 函数时，该函数并不会执行，返回的也不是函数运行结果，而是一个指向内部状态的指针对象（遍历器对象）；下一步必须调用遍历器对象的 next 方法，使得指针移向下一个状态
      也就是说每次调用 next 方法，内部指针就从函数头部或者上一次停下来的地方开始执行，直到遇到下一个 yield 表达式或者 returh 语句，

      Generator 是分段执行的，yield 表达式是暂停执行的标记，而 next 方法可以恢复执行

      yield 使用注意：
        1. yield 表达式如果用在另一个表达式之中，必须放在圆括号里
        2. yield 表达式用作函数参数或者赋值表达式的右边，可以不加括号


      可以将一个 Generator 函数赋值给一个对象的 Symbol.iterator 属性，从而使得该对象具有 Iterator 接口

      yield 本身没有返回值或者说返回的是 undefined，next 方法可以带一个参数，该参数就会被当做上一个 yield 表达式的返回值

      for...of, 扩展运算符(...), 解构赋值和 Array.from 方法内部调用的都是遍历器接口



      Generator.prototype.throw(), 可以在函数体外抛出错误，然后在 Generator 函数体内捕获
        注意点：
          1. 在使用 .throw() 必须先调用一次 next 函数已表明开始执行函数了
          2. throw 函数可接收一个参数，该参数会被 catch 语句接收，建议抛出 Error 实例的对象
          3. 遍历器对象的 throw() 和 全局 throw(). 如果使用 全局 throw 函数抛出的错误, 则只能在函数体外的 catch 语句捕获
          4. Generator 函数的 throw 和全局 throw 互不影响。如果 Generator 函数体内没有部署 try...catch(e){} 代码块，那么 throw 方法抛出的错误将被外部 try...catch 代码捕获，如果外部代码也没有部署 try...catch 则会终止程序的执行
          5. 我们可以给 throw() 传参数, 该参数可以被在 Generator 函数内部的 catch 块接收
          6. Generator.prototype.throw() 方法被捕获之后，会附带执行一次 yield 表达式（也就是说会执行一次 next 方法）
          7. 在 Generator 函数外部，不管是全局 throw 命令还是 Generator 遍历器对象的 throw 函数，都不会影响到遍历器对象的状态的
          8. Generator 函数体外抛出的错误，可以在函数体内捕获；反过来，Generator 函数体内抛出的错误，也可以被函数体外的 catch 捕获
          9. 一旦 Generator 执行过程中抛出错误，且没有被内部捕获，就不会再执行下去了。如果此后还调用 next 方法，将返回一个 value 属性等于 undefined、done为true 的对象；js引擎就会以为这个 Generator 已经结束了

      Generator.prototype.return() 可以返回给定的值，并终结遍历器 Generator 函数
        注意点: 如果 Generator 函数内部有 try...finally 代码块, 且正在执行 try 代码块, 那么 return() 方法会导致立刻进入 finally 代码块, 执行外之后, 整个函数才会结束


      
      yield* 表达式, 用来在一个 Generator 函数内部执行另一个 Generator 函数（任何数据结构只要有 iterator 接口，都可以被 yield* 遍历）
        yield* 后面的 Generator 函数（没有 return 语句时）等同于在 Generator 函数内部部署一个 for...of 循环；在有 return 语句时，则需要用 var value = yield* iterator 的形式获取 return 的值
        yield 后面如果不加星号，返回的是整个数组；加了星号就表示返回的是数组的遍历器对象




      Generator 函数中的 this
        1. Generator 函数总是返回一个遍历器，这个遍历器是 Generator 函数的实例，也继承了 Generator 函数的 prototype 对象上的方法

        ```
        function*    gen.prototype.sayHi = function() {
          console.log('hi!')
        }
        let obj = gen();
        console.log(obj instanceof gen) gen() {}
    
        obj.sayHi()
        ```
        
        2. 但是不能将 Generator 用作构造函数，因为 Generator 函数返回的总是遍历器对象，而不是 this 对象；所以 Generator 函数不能跟 new 命令一起用，会报错

    */

    // 简单例子
    function* hwGen() {
      yield 'hello';
      yield 'world';
      return 'ending';
    }
    const hw = hwGen()
    console.log('hw: ', hw)
    const hwNxt = hw.next()
    console.log('hw next: ', hwNxt)
    const hwNxtNxt = hw.next()
    console.log('hw next next: ', hwNxtNxt)
    const hwNxtNxtNxt = hw.next()
    console.log('hw next next next: ', hwNxtNxtNxt)
    const hwNxtNxtNxtNxt = hw.next()
    console.log('hw next next next next: ', hwNxtNxtNxtNxt)




    // yield 只能用在 Generator 函数内部，用在其他函数内会报错
    function func() {
      var arr = [1, [[2, 3], 4], [5, 6]];
      // 以下会报错，因为 forEach 内函数是普通函数
      // var flat = function* (a) {
      //   a.forEach(function(item) {
      //     if (typeof item !== 'number') {
      //       yield* flat(item)
      //     } else {
      //       yield item;
      //     }
      //   })
      // }

      // 修改为for循环
      function* flat(array) {
        for(let item of array) {
          if (typeof item !== 'number') {
            yield* flat(item)
          } else {
            yield item;
          }
        }
      }


      for(let f of flat(arr)) {
        console.log(f)
      }
    }
    func()




    // 3. yield 使用注意
    function* demo() {
      console.log('hello' + (yield));
      console.log('hello' + (yield 123));
    }
    function* demo01() {
      demo(yield 'a', yield 'b');
      let input = yield;
    }



    // 4. 与 Iterator 接口的关系
    var myObj = {}
    myObj[Symbol.iterator] = function*() {
      yield 1;
      yield 2;
      yield 3;
    }
    // 因为此对象具有了 Symbol.iterator 属性，也便具有了 Iterator 接口，所以可以被 ... 运算符遍历了
    console.log([...myObj])




    // 5. yield 返回值
    function* fGen() {
      const res01 = yield 1;
      console.log('res01: ', res01);
      const res02 = yield 2;
      console.log('res02: ', res02);
      const res03 = yield 3;
      console.log('res03: ', res03);
    }
    const f = fGen()
    const fN01 = f.next()
    console.log('fn: ', fN01)
    const fN02 = f.next('第一个 yield 返回值')
    console.log('fn: ', fN02)
    const fN03 = f.next('第二个 yield 返回值')
    console.log('fn: ', fN03)
    const fN04 = f.next()
    console.log('fn: ', fN04)

    // 第二种方式
    function* fGen01() {
      for(var i = 0; true; i++) {
        var reset = yield i;
        if (reset) {
          i = -1;
          break;
        }
      }
    }
    const fGen01N = fGen01()
    console.log('fGen01: ', fGen01N.next())
    console.log('fGen01: ', fGen01N.next())
    console.log('fGen01: ', fGen01N.next(true))
    console.log('fGen01: ', fGen01N.next())







    // 5. for...of 自动遍历 Generator
    function* foo() {
      yield 1;
      yield 2;
      yield 3;
      yield 4;
      yield 5;
      return 6;
    }
    for(let v of foo()) {
      console.log('foo: ', v)
    }

    // 遍历对象
    function* objectEntries(obj) {
      let propkeys = Reflect.ownKeys(obj)
      for(let propkey of propkeys) {
        yield [propkey, obj[propkey]]
      }
    }
    let jane = { first: 'Jane', last: 'Doe' }
    // key, value:  first Jane
    // key, value:  last Doe
    for(let [key, value] of objectEntries(jane)) {
      console.log('key, value: ', key, value)
    }





    // 6. Generator 函数内处理错误
    function* gGen() {
      try {
        yield;
      } catch(e) {
        console.log('gGen 内部捕获：', e)
      }
    }
    const g = gGen();
    // 执行 next 表明开始执行函数了
    g.next()
    try{
      // 第一次错误会被 Generator 函数内部的 catch 语句捕获
      g.throw('a');
      // 第二次错误会被全局的 catch 语句捕获
      g.throw('b');
    }catch(e) {
      console.log('外部捕获：', e)
    }

    // 4.1 throw 调用时未先调用 next 方法，则程序直接报错
    function* gGen01() {
      try{

      } catch(e) {
        console.log('gGen01: ', e)
      }
    }
    const gGen01Obj = gGen01()
    // Uncaught 未在 throw 调用前调用 next
    gGen01Obj.throw('未在 throw 调用前调用 next')


    // 4.2 如果 Generator 函数内部没有 try...catch(){} 块来捕获抛出的错误，则由外部 try...catch 块来捕获
    function* gInner() {
      while(true) {
        yield;
        console.log('内部捕获:')
      }
    }
    const gInnerNxt = gInner()
    gInnerNxt.next()
    // 外部捕获: a
    try {
      gInnerNxt.throw('a')
      gInnerNxt.throw('b')
    } catch(e) {
      console.log('外部捕获:', e)
    }


    // 4.3 throw 执行之后，会执行一遍 next 方法
    function* Gen() {
      try{
        yield console.log('a');
      } catch(e) {
        console.log('内部捕获：', e)
      }
      yield console.log('b');
      yield console.log('c');
    }
    const gen = Gen()
    // a
    gen.next()
    // 内部捕获： ces
    // b
    gen.throw('失败了')
    // c
    gen.next()


    // 4.4 
    function* gGen02() {
      yield 1;
      console.log('throwing an exception');
      throw new Error('Generator broke!');
      yield 2;
      yield 3;
    }
    function log(generator) {
      var v;
      console.log('starting generator')
      try{
        v = generator.next();
        console.log('第一次执行 next 方法:', v)
      }catch(err) {
        console.log('捕获错误1:', v)
      }
      try {
        v = generator.next();
        console.log('第二次执行 next 方法:', v)
      } catch(err) {
        console.log('捕获错误2:', v)
      }
      try {
        v = generator.next();
        console.log('第三次运行 next 方法: ', v)
      } catch(err) {
        console.log('捕获错误:', v)
      }
      console.log('caller done')
    }
    // starting generator
    // 第一次执行 next 方法: {value: 1, done: false}
    // throwing an exception
    // 捕获错误2: {value: 1, done: false}
    // 第三次运行 next 方法:  {value: undefined, done: true}
    // caller done
    log(gGen02());


    // 5. Generator.prototype.return
    function* gen05() {
      yield 1;
      yield 2;
      yield 3;
      yield 4;
    }
    const gen05Nxt = gen05();
    console.log(gen05Nxt.next())
    // console.log(gen05Nxt.next())
    // 返回给定的值, 并终结 Generator 函数的遍历
    console.log(gen05Nxt.return('a'))
    console.log(gen05Nxt.next())
    console.log(gen05Nxt.next())
    console.log(gen05Nxt.next())
    console.log(gen05Nxt.next())

    // 先执行 finally 代码块
    function* numbers() {
      yield 1;
      try{
        yield 2;
        yield 3;
        yield 4;
      } finally {
        yield 5;
        yield 6;
      }
      yield 7;
    }
    const numbersGen = numbers()
    console.log(numbersGen.next())
    console.log(numbersGen.next())
    // console.log(numbersGen.next())
    console.log(numbersGen.return(8))
    console.log(numbersGen.next())
    console.log(numbersGen.next())
    console.log(numbersGen.next())
    console.log(numbersGen.next())
    console.log(numbersGen.next())
    console.log(numbersGen.next())
  </script>

  <script>
    /*
      yield* 表达式
    */
    // function* foo() {
    //   yield 'a';
    //   yield 'b';
    // }
    // function* bar() {
    //   yield 'x';
    //   // 调用 另一个 Generator 函数, 需要手动遍历
    //   for(let f of foo()) {
    //     console.log(f)
    //   }
    //   yield 'y';
    // }
    // for (let v of bar()) {
    //   console.log(v)
    // }

    // 以上可简写为
    function* foo() {
      yield 'a'
      yield 'b'
    }
    function* bar() {
      yield 'x'
      yield* foo()
      yield 'y'
    }
    // 等同于:
    // function* bar() {
    //   yield 'x'
    //   yield 'a'
    //   yield 'b'
    //   yield 'y'
    // }
    for (let v of bar()) {
      console.log(v)
    }


    // yield* 直接遍历数组
    function* genArray() {
      yield* ['a', 'b', 'c']
    }
    genArray().next()



    // 带有 return 语句
    function* genFunctionWithReturn() {
      yield 'a'
      yield 'b'
      return 'the result'
    }
    function* logReturned(genObj) {
      let result = yield* genObj;
      console.log(result)
    }
    console.log([...logReturned(genFunctionWithReturn())])
  </script>

  <script>
    // Generator 函数中的 this
    // function* F() {
    //   this.a = 1;
    //   yield this.b = 2;
    //   yield this.c = 3;
    // }
    // var obj = {}
    // var f = F.call(obj)
    // f.next()
    // f.next()
    // f.next()

      // 将 obj 和 f 合并
      function* F() {
        this.a = 1;
        yield this.b = 2;
        yield this.c = 3;
      }
      var f = F.call(F.prototype)
  </script>
</body>
</html>